/*
 * VerilogScanner.java
 * 
 * last update: 14.01.2010 by Stefan Saru
 * 
 * author:	Alec(alecu@kermit.cs.pub.ro)
 *
 * Obs: N/A
 */

package ide;

import java.awt.*;
import javax.swing.*;
import javax.swing.text.Segment;


class VerilogScanner {

	/*
	 * don't forget to update the colors if you modify
	 * the indexes !
	 */
	public static final int tokSLComm    = 0;
	public static final int tokMLComm    = 1;
	public static final int tokBinNr     = 2;
	public static final int tokOctNr     = 3;
	public static final int tokDecNr     = 4;
	public static final int tokHexNr     = 5;
	public static final int tokFloatNr   = 6;
	public static final int tokKeyword   = 7;
	public static final int tokPreproc   = 8;
	public static final int tokSysTask   = 9;
	public static final int tokSeparator = 10;
	public static final int tokString    = 11;
	public static final int tokStuff     = 12;

	int tokType;
	int tokLen;
	/**
	 * true if an open "/*" was found on this line
	 */
	boolean openComment;

	char a[];
	int start;
	int stop;

	static String keywords[] = 
	{
		"always", "and", "assign",

		"begin", "buf", "bufif0", "bufif1",

		"case", "casex", "casez", "cmos",

		"deassign", "default", "defparam", "disable",

		"edge", "else", "end", "endcase", "endmodule", "endfunction",
		"endprimitive", "endspecify", "endtable", "endtask", "event",

		"for", "force", "forever", "fork", "function",

		"highz0", "highz1",

		"if", "ifnone", "initial", "inout", "input", "integer",

		"join",

		"large",

		"macromodule", "medium", "module",

		"nand", "negedge", "nmos", "nor", "not", "notif0", "notif1",

		"or", "output",

		"parameter", "pmos", "posedge", "primitive", "pull0", "pull1",
		"pullup", "pulldown",

		"rcmos", "real", "realtime", "reg", "release", "repeat", "rnmos",
		"rpmos", "rtran", "rtranif0", "rtranif1",

		"scalared", "small", "specify", "specparam", "strong0", "strong1",
		"supply1", "supply0",

		"table", "task", "time", "tran", "tranif0", "tranif1", "tri", "tri0",
		"tri1", "triand", "trior", "trireg",

		"vectored",

		"wait", "wand", "weak0", "weak1", "while", "wire", "wor",

		"xnor", "xor"
	};


	static int kSize = keywords.length;

	/**
	 * Stores vectors alphabetically ordered. Each of these vectors
	 * contains all the keywords that start with the same letter.
	 * There should be no more than 30
	 * sorts of starting characters and 15 keywords starting with the same
	 * letter.
	 */
	static char keys[][][] =new char[30][15][];

	/**
	 * How many keys are into each vector.
	 */
	static int keysLengths[] = new int[30];  

	static {
		/*
		 * fetches tthe keywords into keys, which is more
		 * a more reliable structure for the first
		 * matchloop into matchKeyword.
		 */
		char [] buffer;
		for(int i = 0; i < kSize; i++) {
			buffer = keywords[i].toCharArray();
			keys[buffer[0]-'a'][keysLengths[buffer[0] - 'a']++] = buffer;
		}
	}

	char [][] keyBuffer1, keyBuffer2;

	VerilogScanner () {
		// 20 shoul be enough for holding all keywords
		//that start with the same letter
		keyBuffer1 = new char[20][];
		keyBuffer2 = new char[20][];
	}

	/**
	 * The pointer inside the current segment
	 */
	int c;

	final void init(Segment s) {
		a = s.array;
		start = c = s.offset;
		stop = start + s.count -1;
	}


	/**
	 * no more tears !
	 */
	final boolean noMoreTokens() {
		return c > stop;
	}

	final boolean getToken() {
		if (c > stop) return false;
		//c <= stop:
		int lastC = c;
		char curChar = a[c];
		/*System.out.println("current char: " + curChar + " hex: " +
      Integer.toHexString(curChar));*/

		switch(curChar) {
		case ' ':
		case '\t':
		case '\n':
		case '\r':
			tokType = tokStuff;
			do {
				c++;
				if (c > stop) break;
				curChar = a[c];
			} while((curChar == ' ') || (curChar == '\t') ||
					(curChar == '\n') || (curChar == '\r'));

			break;
		case '-':
		case '+':
		case ':':
		case '*':
		case '&':
		case '<':
		case '>':
		case '!':
		case '@':
		case '#':
		case '%':
		case '^':
		case '(':
		case ')':
		case '{':
		case '}':
		case '[':
		case ']':
		case '=':
		case '|':
		case ';':
		case '.':
		case ',':
			tokType = tokSeparator;
			c++;
			break;
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		case '\'':
			tokType = tokDecNr;
			boolean cont;
			loop1:
				do {
					c++;
					cont = false;
					if (c > stop) break;
					curChar = a[c];
					switch (curChar) {
					case 'b':
					case 'B':
						tokType = tokDecNr;
						cont = true;
					case 'o':
					case 'O':
						tokType = tokOctNr;
						cont = true;
					case 'd':
					case 'D':
						cont = true;
					case 'h':
					case 'H':
						tokType = tokHexNr;
						cont = true;;
					case 'e':
					case 'E':
						tokType = tokFloatNr;
						cont = true;
					}
				} while(cont || isDigit(curChar));
			break;
		case '/':
			c++;
			if (c > stop) break;
			curChar = a[c];
			switch(curChar) {
			case '/':
				tokType = tokSLComm;
				c = stop + 1;
				break;
			case '*':
				tokType = tokMLComm;
				openComment = false;
				if (c < stop) {
					c++;
					char c1 = a[c];
					do {
						c++;
						if (c > stop) {
							openComment = true;
							break;
						}
						curChar = c1;
						c1 = a[c];
					} while((c1 != '/') || (curChar != '*'));
				} else openComment = true;
				if (c <= stop) c++; //jump over last '/'
				break;
			default:
				tokType = tokSeparator;
			}
			break;
		case '`':
			tokType = tokPreproc;
			do {
				c++;
				if (c > stop) break;
				curChar = a[c];
			} while(isAlpha(curChar) || isDigit(curChar)
					|| (curChar == '_'));
			break;
		case '\"':
			tokType = tokString;
			do {
				c++;
				if (c > stop) break;
				curChar = a[c];
			} while (curChar != '\"');
			if (c <= stop) c++;
			break;
		case '$':
			tokType = tokSysTask;
			do {
				c++;
				if (c > stop) break;
				curChar = a[c];
			} while(isAlpha(curChar) || isDigit(curChar)
					|| (curChar == '_'));
			break;
		default:
			if (isKeywordConstituent(curChar))
				matchKeyword();
			else {
				tokType = tokStuff;
				c++;
			}
		}
		/*System.out.println("token: [" + (new String(a, lastC, c-lastC )) +
      "] lastC: " + lastC + " c: " + c + " type: " + tokType);*/
		tokLen = c - lastC;
		return true;
	}

	void debug(String s) {
		System.out.println(s);
	}

	/**
	 * Checks whether there is a kwyword starting at tha current
	 * point or not and sets the tokType accordingly. The c pointer
	 * is advanced to the first unmatched character.
	 */
	void matchKeyword() {

		char curChar = a[c];
		int m = 0;
		int charIndex = curChar - 'a';
		int matches = keysLengths[charIndex], matches1;
		char[][] tmp;

		tmp = keyBuffer1;
		keyBuffer1 = keys[charIndex];
		//debug("keymatch for [" + new String(a, c, stop - c + 1) + "]");
		tokType = tokKeyword;

		matchloop:
			while(isKeywordConstituent(curChar)) {
				matches1 = 0;
				for(int i = 0, j = 0;  i < matches; i++) {
					if (m == keyBuffer1[i].length) continue;
					if (curChar == keyBuffer1[i][m]) { //match
						matches1++;
						keyBuffer2[j++] = keyBuffer1[i];
						//debug("matched: [" + new String(keyBuffer2[j-1]) + "]");
					}
				}
				matches = matches1;
				//rotate buffers:
				keyBuffer1 = keyBuffer2;
				keyBuffer2 = tmp;
				tmp = keyBuffer1;
				c++;
				m++;
				if (matches <= 1) break matchloop; //one or no match found
				if (c > stop)  //nothing uniquely found till the end of the line
					break;
				curChar = a[c];
			}

		if (matches == 1) {
			//check the remaining characters :
			int l = keyBuffer1[0].length;
			while(m < l) {
				if (c > stop) {
					tokType = tokStuff;
					return;
				}
				curChar = a[c];
				if (curChar != keyBuffer1[0][m]) {
					while(isAlpha1(curChar)) { //eat the rest
						c++;
						if (c > stop) break;
						curChar = a[c];
					}
					tokType = tokStuff;
					return;
				}
				m++;
				c++;
			}
		} else {
			if (matches > 1) {
				//debug("m: " + m);
				for (int i = 0; i < matches ; i++){
					//debug("matching: [" + new String (keyBuffer1[i]) + "]");
					if (keyBuffer1[i].length == m) {
						//debug("matched keyword: [" + new String (keyBuffer1[i]) + "]");
						tokType = tokKeyword;
						return;
					}
				}
				tokType = tokStuff;
			} else {
				while(isAlpha1(curChar)) { //eat the rest
					c++;
					if (c > stop) break;
					curChar = a[c];
				}
				tokType = tokStuff; //0 matches
			}
		}
	}

	/**
	 * @return true if the multiline comment
	 * ends on this line
	 */
	boolean scanCommentEnd() {
		char curChar1 = 0, curChar2;
		do {
			curChar2 = a[c++];
			if ((curChar1 == '*') && (curChar2 == '/')) {
				tokLen = c - start;
				return true;
			} else
				curChar1 = curChar2;
		} while (c <= stop);
		tokLen = c - start;
		return false;
	}

	/**
	 * We could use isAlpha, but as u see from the keyword table,
	 * there appear only lower case words an tha characters '0' ad '1', so we
	 * can shrink the number of tests.
	 */
	static final boolean isKeywordConstituent(char c) {
		return ((c >= 'a') && (c <= 'z')) || (c == '0') || (c == '1');
	}

	static final boolean isAlpha(char c) {
		return ((c >= 'a') && (c <= 'z')) ||
		((c >= 'A') && (c <= 'Z')) || (c == '_');
	}

	static final boolean isAlpha1(char c) {
		return ((c >= 'a') && (c <= 'z')) ||
		((c >= '0') && (c <= '9')) ||
		((c >= 'A') && (c <= 'Z')) || (c == '_');
	}

	static final boolean isDigit(char c) {
		return (('0' <= c) && (c <= '9')) || (('A' <= c) && (c <= 'F'))
		|| (('a' <= c) && (c <= 'f')) || (c == '_') || (c == 'x') ||
		(c == 'X') || (c == 'z') || (c == 'Z') || (c == '?');
	}

	public static void main (String args[]) {
		try {
			UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
		}
		catch ( Exception e ) {
			System.out.println ("bad: " + e);
			e.printStackTrace();
			System.exit(0);
		}
		UIManager.put(VerilogTextArea.uiClassID, "ide.VerilogTextUI");

		JFrame f = new JFrame();
		VerilogTextArea vt = new VerilogTextArea();
		f.setSize(new Dimension(100,100));
		f.getContentPane().add(vt);
		f.setVisible(true);
	}
}



